package lol.clann.bukkit.pluginboot.util;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lol.clann.bukkit.pluginboot.config.jackson.nbt.NBTConventorNode;
import lol.clann.clannboot.annotation.inject.Inject;
import lol.clann.clannboot.annotation.svc.PostInstance;
import lol.clann.clannboot.annotation.svc.Service;
import lol.clann.clannboot.util.Joiner;
import lol.clann.clannboot.util.JsonUtils;
import lol.clann.clannboot.util.Spliter;
import lol.clann.clannboot.util.clazz.*;
import org.apache.commons.lang3.StringUtils;
import org.bukkit.ChatColor;
import org.nutz.lang.Lang;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author pyz
 * @date 2018/8/7 下午8:39
 */
@Service(priority = Integer.MAX_VALUE)
public class NBTUtils {

    public static final boolean supportNBTTagLongArray = supportNBTTagLongArray();

    public static final WrapClass classNBTBase = ClassUtils.forName("{nms}.NBTBase");
    public static final WrapClass classNBTTagByte = ClassUtils.forName("{nms}.NBTTagByte");
    public static final WrapClass classNBTTagByteArray = ClassUtils.forName("{nms}.NBTTagByteArray");
    public static final WrapClass classNBTTagShort = ClassUtils.forName("{nms}.NBTTagShort");
    public static final WrapClass classNBTTagInt = ClassUtils.forName("{nms}.NBTTagInt");
    public static final WrapClass classNBTTagIntArray = ClassUtils.forName("{nms}.NBTTagIntArray");
    public static final WrapClass classNBTTagLong = ClassUtils.forName("{nms}.NBTTagLong");
    public static final WrapClass classNBTTagLongArray = supportNBTTagLongArray ? ClassUtils.forName("{nms}.NBTTagLongArray") : null;
    public static final WrapClass classNBTTagFloat = ClassUtils.forName("{nms}.NBTTagFloat");
    public static final WrapClass classNBTTagDouble = ClassUtils.forName("{nms}.NBTTagDouble");
    public static final WrapClass classNBTTagString = ClassUtils.forName("{nms}.NBTTagString");
    public static final WrapClass classNBTTagList = ClassUtils.forName("{nms}.NBTTagList");
    public static final WrapClass classNBTTagCompound = ClassUtils.forName("{nms}.NBTTagCompound");

    private static final Map<String, NBTConventorNode> conventorsByKey = new HashMap<>();
    private static final Map<Class, NBTConventorNode> conventorsByClass = new HashMap<>();

    /**
     * 缓存nbt类中的值属性
     */
    private Map<Class, WrapField> valueField = new HashMap<>();

    @Inject
    private NBTCompoundUtils nbtCompoundUtils;
    @Inject
    private NBTListUtils nbtListUtils;
    @Inject
    private ObjectMapper objectMapper;
    @Inject
    private JsonUtils jsonUtils;
    private Constructor constructorNBTTagByte;
    private Constructor constructorNBTTagByteArray;
    private Constructor constructorNBTTagShort;
    private Constructor constructorNBTTagInt;
    private Constructor constructorNBTTagIntArray;
    private Constructor constructorNBTTagLong;
    private Constructor constructorNBTTagLongArray;
    private Constructor constructorNBTTagFloat;
    private Constructor constructorNBTTagDouble;
    private Constructor constructorNBTTagString;
    private Constructor constructorNBTTagList;
    private Constructor constructorNBTTagCompound;

    public static NBTConventorNode getByKey(String key) {
        return conventorsByKey.get(key);
    }

    public static NBTConventorNode getByClass(Class clazz) {
        return conventorsByClass.get(clazz);
    }

    public static NBTConventorNode getByClass(WrapClass clazz) {
        return conventorsByClass.get(clazz.getHandle());
    }

    private static boolean supportNBTTagLongArray() {
        try {
            ClassUtils.forName("{nms}.NBTTagLongArray");
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    @PostInstance
    private void init() throws NoSuchMethodException {
        constructorNBTTagByte = register(classNBTTagByte.getHandle(), byte.class);
        constructorNBTTagByteArray = register(classNBTTagByteArray.getHandle(), byte[].class);
        constructorNBTTagShort = register(classNBTTagShort.getHandle(), short.class);
        constructorNBTTagInt = register(classNBTTagInt.getHandle(), int.class);
        constructorNBTTagIntArray = register(classNBTTagIntArray.getHandle(), int[].class);
        constructorNBTTagLong = register(classNBTTagLong.getHandle(), long.class);
        if (supportNBTTagLongArray) {
            constructorNBTTagLongArray = register(classNBTTagLongArray.getHandle(), long[].class);
        }
        constructorNBTTagFloat = register(classNBTTagFloat.getHandle(), float.class);
        constructorNBTTagDouble = register(classNBTTagDouble.getHandle(), double.class);
        constructorNBTTagString = register(classNBTTagString.getHandle(), String.class);

        constructorNBTTagList = classNBTTagList.getConstructor(ParameterCondition.oempty());
        valueField.put(classNBTTagList.getHandle(), classNBTTagList.getField(FieldFilter.build().static_(false).type(List.class)));

        constructorNBTTagCompound = classNBTTagCompound.getConstructor(ParameterCondition.oempty());
        valueField.put(classNBTTagCompound.getHandle(), classNBTTagCompound.getField(FieldFilter.build().static_(false).type(Map.class)));

        registerConventor(new NBTConventorNode(
                "b ",
                classNBTTagByte.getHandle(),
                nbt -> String.valueOf(getValue(nbt)),
                sValue -> newNBTByte(Byte.valueOf(sValue))));
        registerConventor(new NBTConventorNode(
                "ba",
                classNBTTagByteArray.getHandle(),
                nbt -> Joiner.join((byte[]) getValue(nbt), "[", ",", "]"),
                sValue -> {
                    sValue = sValue.substring(1, sValue.length() - 1);
                    List<String> ss = Spliter.split(",", sValue);
                    byte[] data = new byte[ss.size()];
                    for (int i = 0; i < data.length; i++) {
                        data[i] = Byte.valueOf(ss.get(i));
                    }
                    return newNBTByteArray(data);
                }));
        registerConventor(new NBTConventorNode(
                "s ",
                classNBTTagShort.getHandle(),
                nbt -> String.valueOf(getValue(nbt)),
                sValue -> newNBTShort(Short.valueOf(sValue))));
        registerConventor(new NBTConventorNode(
                "i ",
                classNBTTagInt.getHandle(),
                nbt -> String.valueOf(getValue(nbt)),
                sValue -> newNBTInt(Integer.valueOf(sValue))));
        registerConventor(new NBTConventorNode(
                "ia",
                classNBTTagIntArray.getHandle(),
                nbt -> Joiner.join((int[]) getValue(nbt), "[", ",", "]"),
                sValue -> {
                    sValue = sValue.substring(1, sValue.length() - 1);
                    List<String> ss = Spliter.split(",", sValue);
                    int[] data = new int[ss.size()];
                    for (int i = 0; i < data.length; i++) {
                        data[i] = Integer.valueOf(ss.get(i));
                    }
                    return newNBTIntArray(data);
                }));
        registerConventor(new NBTConventorNode(
                "l ",
                classNBTTagLong.getHandle(),
                nbt -> String.valueOf(getValue(nbt)),
                sValue -> newNBTLong(Long.valueOf(sValue))));
        if (supportNBTTagLongArray) {
            registerConventor(new NBTConventorNode(
                    "la",
                    classNBTTagLongArray.getHandle(),
                    nbt -> Joiner.join((long[]) getValue(nbt), "[", ",", "]"),
                    sValue -> {
                        sValue = sValue.substring(1, sValue.length() - 1);
                        List<String> ss = Spliter.split(",", sValue);
                        long[] data = new long[ss.size()];
                        for (int i = 0; i < data.length; i++) {
                            data[i] = Long.valueOf(ss.get(i));
                        }
                        return newNBTLongArray(data);
                    }));
        }
        registerConventor(new NBTConventorNode(
                "f ",
                classNBTTagFloat.getHandle(),
                nbt -> String.valueOf(getValue(nbt)),
                sValue -> newNBTFloat(Float.valueOf(sValue))));
        registerConventor(new NBTConventorNode(
                "d ",
                classNBTTagDouble.getHandle(),
                nbt -> String.valueOf(getValue(nbt)),
                sValue -> newNBTDouble(Double.valueOf(sValue))));
        registerConventor(new NBTConventorNode(
                "S ",
                classNBTTagString.getHandle(),
                nbt -> String.valueOf(getValue(nbt)),
                sValue -> newNBTString(sValue)));

    }

    /**
     * 把字符串解析为nbt
     *
     * @param content
     * @return
     */
    public Object wrap(String content) {
        try {
            if (content.startsWith("[")) {
                return objectMapper.readValue(content, classNBTTagList.getHandle());
            } else if (content.startsWith("{")) {
                return objectMapper.readValue(content, classNBTTagCompound.getHandle());
            } else {
                return getByKey(content.substring(0, 2)).getDeserializer().apply(content.substring(3));
            }
        } catch (IOException e) {
            throw Lang.wrapThrow(e);
        }
    }

    private void registerConventor(NBTConventorNode conventor) {
        conventorsByKey.put(conventor.getKey(), conventor);
        conventorsByClass.put(conventor.getType(), conventor);
    }

    public Object newNBTByte(byte value) {
        return newInstance(constructorNBTTagByte, value);
    }

    public Object newNBTByteArray(byte[] value) {
        return newInstance(constructorNBTTagByteArray, value);
    }

    public Object newNBTShort(short value) {
        return newInstance(constructorNBTTagShort, value);
    }

    public Object newNBTInt(int value) {
        return newInstance(constructorNBTTagInt, value);
    }

    public Object newNBTIntArray(int[] value) {
        return newInstance(constructorNBTTagIntArray, value);
    }

    public Object newNBTLong(long value) {
        return newInstance(constructorNBTTagLong, value);
    }

    public Object newNBTLongArray(long[] value) {
        if (supportNBTTagLongArray) {
            throw new UnsupportedOperationException();
        }
        return newInstance(constructorNBTTagLongArray, value);
    }

    public Object newNBTFloat(float value) {
        return newInstance(constructorNBTTagFloat, value);
    }

    public Object newNBTDouble(double value) {
        return newInstance(constructorNBTTagDouble, value);
    }

    public Object newNBTString(String value) {
        return newInstance(constructorNBTTagString, value);
    }

    public Object newNBTList() {
        return newInstance(constructorNBTTagList);
    }

    public Object newNBTCompound() {
        return newInstance(constructorNBTTagCompound);
    }

    private Object newInstance(Constructor constructor, Object value) {
        try {
            return constructor.newInstance(value);
        } catch (Exception e) {
            throw Lang.wrapThrow(e);
        }
    }

    private Object newInstance(Constructor constructor) {
        try {
            return constructor.newInstance();
        } catch (Exception e) {
            throw Lang.wrapThrow(e);
        }
    }

    public boolean isNBT(Class clazz) {
        return classNBTBase.getHandle().isAssignableFrom(clazz);
    }

    public boolean isNBT(Object nbtBase) {
        return classNBTBase.isInstance(nbtBase);
    }

    public boolean isTagCompound(Object nbtBase) {
        return classNBTTagCompound.isInstance(nbtBase);
    }

    public boolean isTagList(Object nbtBase) {
        return classNBTTagList.isInstance(nbtBase);
    }

    public boolean isTagString(Object nbtBase) {
        return classNBTTagString.isInstance(nbtBase);
    }

    /**
     * 用于rawMessage
     *
     * @param nbtBase
     * @return
     */
    public String getNBTFormatJson(Object nbtBase) {
        if (isTagCompound(nbtBase)) {
            return nbtCompoundUtils.getNBTFormatJson(nbtBase);
        } else if (isTagList(nbtBase)) {
            return nbtListUtils.getNBTFormatJson(nbtBase);
        } else if (isTagString(nbtBase)) {
            String value = (String) getValue(nbtBase);
            value = value.replace("\"", "\\\"");
            if (StringUtils.isNotBlank(value) && value.endsWith("\\")) {
                value = value + " ";
            }
            return "\"" + value + "\"";
        } else {
            return nbtBase.toString();
        }
    }

    public String toJson(Object nbtBase, boolean withColor) {
        if (nbtBase == null) {
            return null;
        }
        JsonNode node = objectMapper.valueToTree(nbtBase);
        if (withColor) {
            return jsonUtils.toString(node, ChatColor.RED.toString(), ChatColor.WHITE.toString(), ChatColor.GREEN.toString(), ChatColor.WHITE.toString());
        } else {
            return node.toString();
        }
    }

    private Constructor register(Class clazz, Class params) throws NoSuchMethodException {
        valueField.put(clazz, ClassUtils.wrap(clazz).getField(FieldFilter.build().static_(false).type(params)));
        Constructor constructor = clazz.getConstructor(params);
        constructor.setAccessible(true);
        return constructor;
    }

    /**
     * 从nbt对象中取真实值
     *
     * @param nbtBase
     * @return
     */
    public Object getValue(Object nbtBase) {
        if (nbtBase == null) {
            throw new IllegalArgumentException("param can not be nul");
        }
        WrapField field = valueField.get(nbtBase.getClass());
        return field.get(nbtBase);
    }

    /**
     * 直接设置值
     *
     * @param nbtBase
     * @return
     */
    public void setValue(Object nbtBase, Object value) {
        valueField.get(nbtBase.getClass()).set(nbtBase, value);
    }

}
